﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using PSE.Deployment.ConfigurationManager.Controls;
using PSE.Deployment.Engine;
using PSE.Deployment.Engine.CustomEventArgs;
using PSE.Deployment.Engine.Enums;
using PSE.Deployment.Engine.Interfaces;
using PSE.Deployment.SetupBase.Configuration;
using PSE.Deployment.SetupBase.Groups;
using PSE.Deployment.SetupBase.Parameter;
using PSE.Deployment.SetupBase.Parameter.Enums;
using PSE.Deployment.SetupBase.Steps;
using PSE.Deployment.SetupBase.UI;
using PSE.Deployment.Util;
using System.Xml.Linq;

namespace PSE.Deployment.SetupBase
{
    public class SetupMain : ExecutionEngine
    {
        #region Fileds

        private CheckInstalledLayers _checkInstalledLayers;
        private Steps.UpdateInfo _updateInfo;
        private Steps.InstallationMode _installationMode;
        private string _configurationFilePath;
        private string _instalationSource;

        #endregion

        #region Properties

        public string SystemId { get; set; }

        protected PSE.Framework.Common.Version SystemVersion { get; set; }

        protected string ParametersFilePath { get; set; }

        public string HosterName { get; set; }

        public SetupParameter Parameter { get; set; }

        public int ExitCode { get; set; }

        public SetupConfiguration Configuration
        {
            get
            {
                return SetupMainFactory.GetInstance().Parameter.SetupConfiguration;
            }
        }

        public SetupConfiguration OriginalConfiguration { get; set; }

        public SetupConfiguration ClonedConfiguration { get; set; }

        #endregion

        #region Constructor

        public SetupMain()
        {
            _configurationFilePath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "SetupConfiguration.xml");

            if (!System.IO.File.Exists(_configurationFilePath))
            {
                SetExitCode();
                throw new Exception(string.Format("Não foi possível localizar o arquivo de configuração do Setup no caminho {0}.", _configurationFilePath));
            }
            else
            {
                GetSystemInformation();
            }

            string oldParametersFilePath_v1 = string.Format(@"{0}\Bvmf\{1}\Configuration\Setup{1}Parameters.xml", PSE.Deployment.Util.Windows.GetFolderPath(PSE.Deployment.Util.Windows.SpecialFolder.ApplicationData), SystemId);
            string oldParametersFilePath_v2 = string.Format(@"{0}\BMFBOVESPA\{1}\{1}Parameters.xml", PSE.Deployment.Util.Windows.GetFolderPath(PSE.Deployment.Util.Windows.SpecialFolder.ApplicationData), SystemId);

            if (System.IO.File.Exists(oldParametersFilePath_v1) || System.IO.File.Exists(oldParametersFilePath_v2))
            {
                SetExitCode();
                throw new Exception(string.Format("Existe uma versão do {0} instalado, por favor desisntale-a antes de prosseguir.", SystemId));
            }

            this.Id = "SetupMain";
            this.Description = "SetupMain";

            this.ErrorBehavior = StepErrorBehavior.CancelParent;
            this.CancelBehavior = StepGroupCancelBehavior.CancelParent;
            this.NavigationBehavior = StepNavigationBehavior.NextCancel;

            this.ExecutionStarted += new EventHandler<ExecutionStartedEventArgs>(SetupMain_ExecutionStarted);
            this.ExecutionCanceled += new EventHandler<ExecutionCanceledEventArgs>(SetupMain_ExecutionCanceled);
            this.ExecutionFinished += new EventHandler<ExecutionFinishedEventArgs>(SetupMain_ExecutionFinished);
            this.ExecutionError += new EventHandler<ExecutionErrorEventArgs>(SetupMain_ExecutionError);

            _checkInstalledLayers = new CheckInstalledLayers(this);
            _checkInstalledLayers.ExecutionFinished += new EventHandler<ExecutionFinishedEventArgs>(_checkInstalledLayers_ExecutionFinished);
            this.Steps.Add(_checkInstalledLayers);

            _installationMode = new Steps.InstallationMode(this);
            _installationMode.ExecutionFinished += new EventHandler<ExecutionFinishedEventArgs>(_installationMode_ExecutionFinished);

            CreateParameter();

            LoadDefaultConfiguration();
        }

        #endregion

        #region Group Events

        protected virtual void SetupMain_ExecutionStarted(object sender, ExecutionStartedEventArgs e)
        {
            if (e.OriginalSource is SetupExecutionGroup)
            {
                UpdateModulesVersion();
            }
        }

        protected virtual void SetupMain_ExecutionFinished(object sender, ExecutionFinishedEventArgs e)
        {
            SetIsInstalled();

            FinishGroupEvent(e);
        }

        protected virtual void SetupMain_ExecutionCanceled(object sender, ExecutionCanceledEventArgs e)
        {
            SetIsInstalled();

            if (SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.Uninstall &&
                SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.Repair)
            {
                if (e.OriginalSource is IStepGroup)
                {
                    if (((IStepGroup)e.OriginalSource).CancelBehavior == StepGroupCancelBehavior.NotifyParent && SetupMainFactory.GetInstance().Parameter.IsCancelling)
                    {
                        //GroupCancelActionSelection msg = new GroupCancelActionSelection(e.OriginalSource.Description + " foi cancelado, selecione uma das opções abaixo.");
                        //e.CancelAction = msg.ShowDialog();
                        e.CancelAction = StepGroupCancelAction.CancelParent;

                        SetupMainFactory.GetInstance().Parameter.IsCancelling = false;

                        SetupMainFactory.GetInstance().ContinueExecution();
                    }
                }
            }

            FinishGroupEvent(e);
        }

        protected virtual void SetupMain_ExecutionError(object sender, ExecutionErrorEventArgs e)
        {
            if (e.OriginalSource.ErrorBehavior == StepErrorBehavior.CancelParent)
            {
                SetExitCode(-2);
                ConfigurationMessageBox.Show(string.Format("Ocorreu um erro crítico e o Setup será fechado, verifique o log na pasta '{0}' para maiores detalhes.", this.LogPath), "Erro", ConfigurationMessageBoxButton.Ok, ConfigurationMessageBoxImage.Error);
            }
        }

        private void FinishGroupEvent(StepEventArgs e)
        {
            if (e.OriginalSource == this)
            {
                SaveOrDeleteParameter();
            }
            else if (e.OriginalSource is SetupGroup)
            {
                if (SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.HotFix &&
                    SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.ServicePack &&
                    SetupMainFactory.GetInstance().Parameter.IsInstalled)
                {
                    SaveParameters();
                }
            }
        }

        private void SetIsInstalled()
        {
            SetupMainFactory.GetInstance().Parameter.IsInstalled = SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Groups.
                        SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).Any(g => g.Installed);
        }

        #endregion

        #region Step Events

        private void _checkInstalledLayers_ExecutionFinished(object sender, ExecutionFinishedEventArgs e)
        {
            if (!SetupMainFactory.GetInstance().Parameter.HasInvalidPrograms)
            {
                if (SetupMainFactory.GetInstance().Parameter.IsInstalled)
                {
                    ConfigureSetupMode();
                }
                else
                {
                    if (SetupMainFactory.GetInstance().Parameter.ShowWizard)
                    {
                        AddWizardStep();
                    }
                    else
                    {
                        SetupMainFactory.GetInstance().Parameter.SetupMode = SetupMode.Install;
                        AddInstallStep();
                    }
                }
            }
        }

        private void _installationMode_ExecutionFinished(object sender, ExecutionFinishedEventArgs e)
        {
            ConfigureSetupMode();
        }

        private void _updateInfo_ExecutionFinished(object sender, ExecutionFinishedEventArgs e)
        {
            if (e.NavigationAction != StepNavigationAction.Cancel)
            {
                AddUpdateStep();
            }
        }

        #endregion

        #region Configure Selecion Mode

        private void ConfigureSetupMode()
        {
            string message;

            switch (SetupMainFactory.GetInstance().Parameter.SetupMode)
            {
                case SetupMode.None:
                case SetupMode.Install:

                    if (ValidateReleaseVersion(out message))
                    {
                        if (!this.Steps.Contains(_installationMode))
                        {
                            this.Steps.Add(_installationMode);
                        }
                    }
                    else
                    {
                        SetExitCode(-2);
                        ConfigurationMessageBox.Show(message, "Atenção", ConfigurationMessageBoxButton.Ok, ConfigurationMessageBoxImage.Exclamation);
                    }

                    break;

                case SetupMode.Uninstall:

                    Configuration.SystemInfo.Groups.SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).ToList().ForEach(g => g.Install = false);
                    AddInstallStep();
                    break;

                case SetupMode.Change:

                    if (ValidateReleaseVersion(out message))
                    {
                        AddWizardStep();
                    }
                    else
                    {
                        SetExitCode(-2);
                        ConfigurationMessageBox.Show(message, "Atenção", ConfigurationMessageBoxButton.Ok, ConfigurationMessageBoxImage.Exclamation);
                    }

                    break;

                case SetupMode.Repair:

                    if (ValidateReleaseVersion(out message))
                    {
                        AddRepairStep();
                    }
                    else
                    {
                        SetExitCode(-2);
                        ConfigurationMessageBox.Show(message, "Atenção", ConfigurationMessageBoxButton.Ok, ConfigurationMessageBoxImage.Exclamation);
                    }

                    break;

                case SetupMode.HotFix:
                case SetupMode.ServicePack:

                    if (ValidateHotfixVersion(out message))
                    {
                        if (ValidateGroups(out message))
                        {
                            if (SetupMainFactory.GetInstance().Parameter.ShowWizard)
                            {
                                _updateInfo = new Steps.UpdateInfo(this);
                                _updateInfo.ExecutionFinished += new EventHandler<ExecutionFinishedEventArgs>(_updateInfo_ExecutionFinished);
                                this.Steps.Add(_updateInfo);
                            }
                            else
                            {
                                AddUpdateStep();
                            }
                        }
                        else
                        {
                            SetExitCode(0);
                            ConfigurationMessageBox.Show(message, "Atenção", ConfigurationMessageBoxButton.Ok, ConfigurationMessageBoxImage.Exclamation);
                        }
                    }
                    else
                    {
                        SetExitCode(-2);
                        ConfigurationMessageBox.Show(message, "Atenção", ConfigurationMessageBoxButton.Ok, ConfigurationMessageBoxImage.Exclamation);
                    }

                    break;
            }
        }

        #endregion

        #region Version Control

        public void UpdateModulesVersion()
        {
            if (SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.Install ||
                SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.Uninstall ||
                SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.Change ||
                SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.Repair)
            {
                SetupMainFactory.GetInstance().Parameter.SystemVersion = SetupMainFactory.GetInstance().OriginalConfiguration.SystemInfo.Version;

                IEnumerable<Group> groups = SetupMainFactory.GetInstance().Configuration.SystemInfo.Groups.SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).Distinct(new GroupComparer());

                var modules = (from g in groups
                               from Layer in g.Layers
                               where (g.Install == null || g.Install.GetValueOrDefault()) && !g.Installed
                               where !string.IsNullOrEmpty(g.Module.Name)
                               select new { g.Module.Id, g.Module.Name, Layer }).Distinct();

                foreach (var module in modules)
                {
                    ModuleVersion moduleVersion = SetupMainFactory.GetInstance().Parameter.Versions.Find(m => m.Id == module.Id);

                    if (moduleVersion == null)
                    {
                        SetupMainFactory.GetInstance().Parameter.Versions.Add(
                            new ModuleVersion(module.Id, module.Name, SetupMainFactory.GetInstance().Parameter.SystemVersion,
                                new List<ModuleLayer>() { new ModuleLayer(module.Layer, SetupMainFactory.GetInstance().Parameter.SystemVersion) }));
                    }
                    else
                    {
                        moduleVersion.Version = SetupMainFactory.GetInstance().Parameter.SystemVersion;

                        ModuleLayer moduleLayer = moduleVersion.Layers.Find(l => l.Layer == module.Layer);

                        if (moduleLayer == null)
                        {
                            moduleVersion.Layers.Add(new ModuleLayer(module.Layer, SetupMainFactory.GetInstance().Parameter.SystemVersion));
                        }
                        else 
                        {
                            moduleLayer.Version = SetupMainFactory.GetInstance().Parameter.SystemVersion;
                        }
                    }
                }
            }
            else if (SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.HotFix ||
                     SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.ServicePack)
            {
                UpdateHotFixVersion();
            }
        }

        private void UpdateHotFixVersion()
        {
            string key = WindowsRegistry.REGISTRY_UNINSTALL_PATH + SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.ToString();
            string keyModules = "Modules";

            var modules = (from g in SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Groups
                           from Layer in g.Layers
                           where (g.Installed == true)
                           where (g.Install == null || g.Install.GetValueOrDefault())
                           where !string.IsNullOrEmpty(g.Module.Name)
                           select new { g.Module.Id, g.Module.Name, Layer }).Distinct();

            foreach (var module in modules)
            {
                ModuleVersion installedModuleVersion = SetupMainFactory.GetInstance().Parameter.Versions.Find(v => v.Id == module.Id);

                if (installedModuleVersion != null)
                {
                    //Altera a versão do módulo no Xml Configuration
                    installedModuleVersion.Version = SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Version;

                    //Altera a versão do módulo no Registro do Windows
                    Microsoft.Win32.RegistryKey subKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(key + @"\" + keyModules + @"\" + installedModuleVersion.Id, true);
                    if (subKey != null)
                    {
                        subKey.SetValue("Version", installedModuleVersion.Version.ToString());
                    }

                    //Altera a versão da camada do módulo
                    ModuleLayer moduleLayer = installedModuleVersion.Layers.Find(l => l.Layer == module.Layer);
                    if (moduleLayer != null)
                    {
                        moduleLayer.Version = installedModuleVersion.Version;

                        Microsoft.Win32.RegistryKey subKeyLayer = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(key + @"\" + keyModules + @"\" + installedModuleVersion.Id + @"\" + moduleLayer.Layer.ToString());
                        subKeyLayer.SetValue("Version", moduleLayer.Version.ToString());
                    }
                }
            }
        }

        private bool ValidateReleaseVersion(out string message)
        {
            bool isValid = true;
            message = string.Empty;

            if (SetupMainFactory.GetInstance().OriginalConfiguration.SystemInfo.Version < SetupMainFactory.GetInstance().Configuration.SystemInfo.Version)
            {
                isValid = false;
                message = string.Format("A versão {0} é inferior a versão instalada {1}. Portanto, não é compatível. É necessário desinstalar antes de proseguir.", SetupMainFactory.GetInstance().OriginalConfiguration.SystemInfo.Version.ToString(), SetupMainFactory.GetInstance().Configuration.SystemInfo.Version.ToString());
            }
            else if (SetupMainFactory.GetInstance().OriginalConfiguration.SystemInfo.Version > SetupMainFactory.GetInstance().Configuration.SystemInfo.Version)
            {
                if (SetupMainFactory.GetInstance().Configuration.SystemInfo.Version.Major != SetupMainFactory.GetInstance().OriginalConfiguration.SystemInfo.Version.Major)
                {
                    isValid = false;
                    message = string.Format("A versão {0} não é compatível com a versão instalada {1}. É necessário desinstalar antes de proseguir.", SetupMainFactory.GetInstance().OriginalConfiguration.SystemInfo.Version.ToString(), SetupMainFactory.GetInstance().Configuration.SystemInfo.Version.ToString());
                }
            }

            return isValid;
        }

        private bool ValidateHotfixVersion(out string message)
        {
            bool isValid = true;
            message = string.Empty;

            List<Module> modules = (from g in SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Groups
                                    select g.Module).Distinct(new ModuleComparer()).ToList();

            if (modules.Count == 0)
            {
                isValid = false;
                message = string.Format("A versão {0} não possui nenhum módulo.", SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Version.ToString());
            }

            foreach (Module module in modules)
            {
                PSE.Framework.Common.Version newVersion = SetupMainFactory.GetInstance().Configuration.SystemInfo.Version;

                ModuleVersion installedModuleVersion = SetupMainFactory.GetInstance().Parameter.Versions.Find(v => v.Id == module.Id);

                if (installedModuleVersion != null)
                {
                    if (installedModuleVersion.Version == newVersion)
                    {
                        isValid = false;
                        AddNewLine(message, out message);
                        message += string.Format("A versão {0} para o módulo {1} já está instalada.", newVersion.ToString(), module.Name);
                    }
                    else
                    {
                        if (installedModuleVersion.Version.Major != newVersion.Major ||
                            installedModuleVersion.Version.Minor != newVersion.Minor)
                        {
                            isValid = false;
                            AddNewLine(message, out message);
                            message += string.Format("A versão {0} para o módulo {1} não é compatível com a versão instalada {2}. Para proseguir é necessário instalar a versão {3} do módulo {1}.", newVersion.ToString(), module.Name, installedModuleVersion.Version.ToString(), new PSE.Framework.Common.Version(newVersion.Major, newVersion.Minor, 0, 0).ToString());
                        }
                        else
                        {
                            if (installedModuleVersion.Version.MajorRevision < newVersion.MajorRevision)
                            {
                                if (newVersion.MinorRevision > 0)
                                {
                                    isValid = false;
                                    AddNewLine(message, out message);
                                    message += string.Format("É necessário instalar o Service Pack {0} para o módulo {1} antes de prosseguir.", new PSE.Framework.Common.Version(newVersion.Major, newVersion.Minor) { MajorRevision = newVersion.MajorRevision, MinorRevision = 0, Build = newVersion.Build }.ToString(), module.Name);
                                }
                            }
                            else if (installedModuleVersion.Version.MajorRevision == newVersion.MajorRevision &&
                                    (installedModuleVersion.Version.MinorRevision + 1) < newVersion.MinorRevision)
                            {
                                isValid = false;
                                AddNewLine(message, out message);
                                message += string.Format("É necessário instalar o(s) HotFix {0} para o módulo {1} antes de prosseguir.", GetRepairs(installedModuleVersion.Version, newVersion), module.Name);
                            }
                            else if (newVersion < installedModuleVersion.Version)
                            {
                                isValid = false;
                                AddNewLine(message, out message);
                                message += string.Format("Não é permitido instalar uma versão menor ({0}) do que a instalada ({1}).", newVersion.ToString(), installedModuleVersion.Version.ToString());
                            }
                        }
                    }
                }
            }

            return isValid;
        }

        private void AddNewLine(string msg, out string message)
        {
            if (!string.IsNullOrEmpty(msg))
                msg += "\n\n";

            message = msg;
        }

        private bool ValidateGroups(out string message)
        {
            bool isValid = true;
            message = string.Empty;

            List<Group> installedGroups = SetupMainFactory.GetInstance().ClonedConfiguration.SystemInfo.Groups.
                SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).Where(g => (g.Install == null || g.Install.GetValueOrDefault()) && g.Installed).Distinct(new GroupComparer()).ToList();

            var groups = from g in SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Groups
                         from ig in installedGroups
                         where g.Id == ig.Id
                         select g;

            //quando não for encontrado grupos o setup deve modificar a versão do sistema.
            if (groups.Count() == 0)
            {
                isValid = false;

                message = "As atualizações foram instaladas com sucesso";

                //altera a versão
                UpdateHotFixVersion();

                //muda o estado dos grupos para instalado
                SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Groups.SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).ToList().ForEach(g => g.Installed = true);
            }

            return isValid;
        }

        private string GetRepairs(PSE.Framework.Common.Version installedModuleVersion, PSE.Framework.Common.Version version)
        {
            StringBuilder repairs = new StringBuilder();

            for (int i = installedModuleVersion.MinorRevision + 1; i < version.MinorRevision; i++)
            {
                PSE.Framework.Common.Version v = new PSE.Framework.Common.Version(version.Major, version.Minor) { MajorRevision = version.MajorRevision, MinorRevision = (short)i, Build = version.Build };
                repairs.Append(v.ToString() + ", ");
            }

            if (repairs.Length > 0)
                return repairs.ToString().Substring(0, repairs.Length - 2);
            else
                return string.Empty;
        }

        private void GetSystemInformation()
        {
            XDocument document = XDocument.Load(_configurationFilePath);

            List<XElement> elements = document.Descendants().ToList();

            XElement systemElement = elements.Find(e => e.Name.LocalName == "SystemInfo");

            if (systemElement != null)
            {
                this.SystemId = systemElement.Attribute("Id").Value;

                XElement versionElement = systemElement.Element("Version");

                if (versionElement != null &&
                    versionElement.Element("Major") != null &&
                    versionElement.Element("Minor") != null &&
                    versionElement.Element("MajorRevision") != null &&
                    versionElement.Element("MinorRevision") != null &&
                    versionElement.Element("Build") != null)
                {
                    this.SystemVersion = new PSE.Framework.Common.Version();
                    this.SystemVersion.Major = Convert.ToInt32(versionElement.Element("Major").Value);
                    this.SystemVersion.Minor = Convert.ToInt32(versionElement.Element("Minor").Value);
                    this.SystemVersion.MajorRevision = Convert.ToInt16(versionElement.Element("MajorRevision").Value);
                    this.SystemVersion.MinorRevision = Convert.ToInt16(versionElement.Element("MinorRevision").Value);
                    this.SystemVersion.Build = Convert.ToInt32(versionElement.Element("Build").Value);
                }
                else
                {
                    SetExitCode();
                    throw new Exception(string.Format("Não foi possível localizar as informações da versão do sistema no arquivo {0}.", _configurationFilePath));
                }
            }
            else
            {
                SetExitCode();
                throw new Exception(string.Format("Não foi possível localizar as informações de sistema no arquivo {0}.", _configurationFilePath));
            }
        }

        #endregion

        #region Save / Delete / Load Files

        private void LoadDefaultConfiguration()
        {
            string parametersFilePath = string.Format(@"{0}\BMFBOVESPA\{1}\{1}Parameters_v{2}.xml", PSE.Deployment.Util.Windows.GetFolderPath(PSE.Deployment.Util.Windows.SpecialFolder.ApplicationData), SystemId, this.SystemVersion.Major.ToString());

            this.OriginalConfiguration = (SetupConfiguration)Serializer.Deserialize(GetConfigurationType(), _configurationFilePath);

            if (System.IO.File.Exists(parametersFilePath))
            {
                this.Parameter = (SetupParameter)Serializer.Deserialize(GetParameterType(), parametersFilePath);

                if (this.OriginalConfiguration.SystemInfo.Version > this.Parameter.SystemVersion)
                {
                    LoadPreviousSettings(OriginalConfiguration, this.Parameter.SetupConfiguration);
                }

                this.Parameter.IsInstalled = true;
            }
            else
            {
                this.Parameter.SetupConfiguration = OriginalConfiguration;
                this.Parameter.ParametersFilePath = parametersFilePath;
                this.Parameter.InstalationSource = System.Windows.Forms.Application.StartupPath;
                this.Parameter.InstallationPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), Parameter.SetupConfiguration.DefaultInstallPath);
                this.Parameter.IsInstalled = false;
            }

            this.Parameter.FullInstallationPath = Path.Combine(this.Parameter.InstallationPath, SystemId);
            this.ParametersFilePath = parametersFilePath;
        }

        private void LoadPreviousSettings(SetupConfiguration newConfiguration, SetupConfiguration oldConfiguration)
        {
            List<Group> newGroups = newConfiguration.SystemInfo.Groups.SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).Distinct(new GroupComparer()).ToList();
            List<Group> oldGroups = oldConfiguration.SystemInfo.Groups.SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).Distinct(new GroupComparer()).ToList();

            //Lista os grupos ques estão instalados e estão na configuração atual
            var groups = (from NewGroup in newGroups
                          from OldGroup in oldGroups
                          where OldGroup.Installed && OldGroup.Id == NewGroup.Id
                          select new { NewGroup, OldGroup });

            //Lista todas as ações do tipo UpdateConfig dos grupos listados acima
            var groupsUpdateConfig = from g in groups
                                     from ActionFromNewGroup in g.NewGroup.Actions
                                     from ActionFromOldGroup in g.OldGroup.Actions
                                     where ActionFromNewGroup is Configuration.Actions.UpdateConfig &&
                                           ActionFromOldGroup is Configuration.Actions.UpdateConfig
                                     select new { ActionFromNewGroup, ActionFromOldGroup };

            //Lista todos os arquivos de configurações que foram instalados e estão na configuração atual
            var xmlFiles = from u in groupsUpdateConfig
                           from FileFromNewGroup in ((Configuration.Actions.UpdateConfig)u.ActionFromNewGroup).XmlFiles
                           from FileFromOldGroup in ((Configuration.Actions.UpdateConfig)u.ActionFromOldGroup).XmlFiles
                           where FileFromNewGroup.Folder == FileFromOldGroup.Folder
                           select new { FileFromNewGroup, FileFromOldGroup };

            //Lista todas as tags dos os arquivos de configurações que foram instalados e estão na configuração atual
            //var tags = from f in xmlFiles
            //           from TagFromNewGroup in f.FileFromNewGroup.Tags
            //           from TagFromOldGroup in f.FileFromOldGroup.Tags
            //           where TagFromNewGroup.XPath == TagFromOldGroup.XPath
            //           select new { TagFromNewGroup, TagFromOldGroup };


            //Lista todas as tags dos os arquivos de configurações que foram instalados e estão na configuração atual
            var tags = from f in xmlFiles
                       from TagFromNewGroup in f.FileFromNewGroup.Tags
                       from TagFromOldGroup in f.FileFromOldGroup.Tags
                       from xPathListFromNewGroup in TagFromNewGroup.XPathList
                       from xPathListFromOldGroup in TagFromOldGroup.XPathList
                       where xPathListFromNewGroup.XPath == xPathListFromOldGroup.XPath
                       select new { TagFromNewGroup, TagFromOldGroup };

            //Lista todos os atributos das tags dos os arquivos de configurações que foram instalados e estão na configuração atual
            var attributes = from t in tags
                             from AttributeFromNewGroup in t.TagFromNewGroup.Attributes
                             from AttributeFromOldGroup in t.TagFromOldGroup.Attributes
                             where AttributeFromNewGroup.Name == AttributeFromOldGroup.Name
                             select new { AttributeFromNewGroup, AttributeFromOldGroup };

            //Lista todas as ações do tipo UpdateWindowsRegistry dos grupos listados acima
            var groupsUpdateWindowsRegistry = from g in groups
                                              from ActionFromNewGroup in g.NewGroup.Actions
                                              from ActionFromOldGroup in g.OldGroup.Actions
                                              where ActionFromNewGroup is Configuration.Actions.UpdateWindowsRegistry &&
                                                    ActionFromOldGroup is Configuration.Actions.UpdateWindowsRegistry
                                              select new { ActionFromNewGroup, ActionFromOldGroup };

            //Lista todas as chaves de registro que foram instalados e estão na configuração atual
            var keys = from r in groupsUpdateWindowsRegistry
                       from KeyFromNewGroup in ((Configuration.Actions.UpdateWindowsRegistry)r.ActionFromNewGroup).Keys
                       from KeyFromOldGroup in ((Configuration.Actions.UpdateWindowsRegistry)r.ActionFromOldGroup).Keys
                       where KeyFromNewGroup.Name == KeyFromOldGroup.Name
                       select new { KeyFromNewGroup, KeyFromOldGroup };

            //Lista todos os valores das chaves de registro que foram instalados e estão na configuração atual
            var registryValues = from k in keys
                                 from RegistryValueFromNewGroup in k.KeyFromNewGroup.Values
                                 from RegistryValueFromOldGroup in k.KeyFromOldGroup.Values
                                 where RegistryValueFromNewGroup.Name == RegistryValueFromOldGroup.Name
                                 select new { RegistryValueFromNewGroup, RegistryValueFromOldGroup };

            //Marca todos os grupos que somente existem na nova configuração para serem instalados
            newGroups.Where(newGroup => oldGroups.Find(oldGroup => oldGroup.Id == newGroup.Id) == null).ToList().
                ForEach(g => { g.Install = true; g.Installed = false; SetStateParentGroup(g, newGroups); });

            foreach (var g in groups)
            {
                g.NewGroup.Install = g.OldGroup.Install;
                g.NewGroup.Installed = g.OldGroup.Installed;
            }

            foreach (var tag in tags)
            {
                tag.TagFromNewGroup.Value = tag.TagFromOldGroup.Value;
            }

            foreach (var attribute in attributes)
            {
                attribute.AttributeFromNewGroup.Value = attribute.AttributeFromOldGroup.Value;
            }

            foreach (var registryValue in registryValues)
            {
                registryValue.RegistryValueFromNewGroup.Value = registryValue.RegistryValueFromOldGroup.Value;
            }
        }

        private void SetStateParentGroup(Group childGroup, List<Group> newGroups)
        {
            if (childGroup.ParentGroup != null)
            {
                Group parentGroup = newGroups.Find(g => g.Id == childGroup.ParentGroup.Id);

                if (parentGroup != null)
                {
                    IEnumerable<Group> childGroups = newGroups.Where(g => g.ParentGroup != null && g.ParentGroup.Id == parentGroup.Id);

                    if (childGroups.Count() > 0)
                    {
                        if (childGroups.All(g => g.Install == true))
                        {
                            parentGroup.Install = true;
                        }
                        else
                        {
                            parentGroup.Install = null;
                        }
                    }
                    else
                    {
                        parentGroup.Install = true;
                    }

                    parentGroup.Installed = false;

                    SetStateParentGroup(parentGroup, newGroups);
                }
            }
        }

        public void LoadParameters(string parametersFilePath)
        {
            if (System.IO.File.Exists(parametersFilePath))
            {
                SetupMainFactory.GetInstance().Parameter = (SetupParameter)Serializer.Deserialize(GetParameterType(), parametersFilePath);
                SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Groups.SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).Where(g => g.Install.GetValueOrDefault() == true).ToList().ForEach(g => g.Installed = false);
                SetupMainFactory.GetInstance().Parameter.InstalledArtefacts.Clear();
                SetupMainFactory.GetInstance().Parameter.IsInstalled = false;
            }
        }

        public void LoadConfiguration(string configurationFilePath)
        {
            if (System.IO.File.Exists(configurationFilePath))
            {
                _instalationSource = SetupMainFactory.GetInstance().Parameter.InstalationSource;
                ClonedConfiguration = (SetupConfiguration)CloneHelper.Clone(SetupMainFactory.GetInstance().Parameter.SetupConfiguration);

                SetupMainFactory.GetInstance().Parameter.SetupConfiguration = (SetupConfiguration)Serializer.Deserialize(GetConfigurationType(), configurationFilePath);

                SetupMainFactory.GetInstance().Parameter.InstalationSource = System.Windows.Forms.Application.StartupPath;

                SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.Groups.SelectDeep(g => g.SubGroups).SelectDeep(g => g.Dependencies).ToList().ForEach(g => { g.Install = true; g.Installed = false; });

                LoadPreviousSettings(this.Parameter.SetupConfiguration, ClonedConfiguration);
            }
        }

        private void SaveOrDeleteParameter()
        {
            DeleteHotfixFiles();

            if (SetupMainFactory.GetInstance().Parameter.IsInstalled)
            {
                if (SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.HotFix ||
                    SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.ServicePack)
                {
                    SetupMainFactory.GetInstance().Parameter.SetupConfiguration = ClonedConfiguration;
                    SetupMainFactory.GetInstance().Parameter.InstalationSource = _instalationSource;
                }

                if (this.OriginalConfiguration.SystemInfo.Version > this.Parameter.SystemVersion)
                {
                    this.Parameter.SetupConfiguration = OriginalConfiguration;
                }

                SetupMainFactory.GetInstance().Parameter.SetupMode = SetupMode.None;

                SetupMainFactory.GetInstance().Parameter.IsCancelling = false;

                SaveParameters();
            }
            else
            {
                if (SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.HotFix &&
                    SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.ServicePack)
                {
                    DeleteParameters();
                }
            }
        }

        private void SaveParameters()
        {
            Serializer.Serialize(GetParameterType(), SetupMainFactory.GetInstance().Parameter, this.ParametersFilePath);
        }

        public void DeleteParameters()
        {
            if (SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.HotFix &&
                SetupMainFactory.GetInstance().Parameter.SetupMode != SetupMode.ServicePack)
            {
                if (!string.IsNullOrEmpty(this.ParametersFilePath) && System.IO.File.Exists(this.ParametersFilePath))
                    System.IO.File.Delete(this.ParametersFilePath);

                if (Directory.Exists(Path.GetDirectoryName(this.ParametersFilePath)))
                    PSE.Deployment.Util.File.DeleteEmptyDirectories(SetupMainFactory.GetInstance().Parameter.InstallationPath, Path.GetDirectoryName(this.ParametersFilePath));

                PSE.Deployment.Util.WindowsRegistry.RemoveInstalledInformation(SetupMainFactory.GetInstance().Parameter.SetupConfiguration.SystemInfo.ToString());
            }
        }

        private void DeleteHotfixFiles()
        {
            if (SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.HotFix ||
                SetupMainFactory.GetInstance().Parameter.SetupMode == SetupMode.ServicePack)
            {
                string hotfixfolder = Path.Combine(System.Windows.Forms.Application.StartupPath, "Bin");

                if (Directory.Exists(hotfixfolder))
                {
                    try
                    {
                        Directory.Delete(hotfixfolder, true);
                    }
                    catch (Exception ex)
                    {
                        this.WriteStepLog(ex.ToString());

                        SetExitCode(-2);
                    }
                }
            }
        }

        #endregion

        #region Virtual Methods

        protected virtual void AddInstallStep()
        {
            this.Steps.Add(new SetupInstall(this));
        }

        protected virtual void AddRepairStep()
        {
            this.Steps.Add(new SetupRepair(this));
        }

        protected virtual void AddChangeStep()
        {
            this.Steps.Add(new SetupChange(this));
        }

        protected virtual void AddUpdateStep()
        {
            this.Steps.Add(new SetupUpdate(this));
        }

        protected virtual void AddWizardStep()
        {

        }

        protected virtual Type GetConfigurationType()
        {
            return typeof(SetupConfiguration);
        }

        protected virtual Type GetParameterType()
        {
            return typeof(SetupParameter);
        }

        protected virtual void CreateParameter()
        {
            this.Parameter = new SetupParameter();
        }

        #endregion

        #region Public Methods

        public void SetExitCode()
        {
            SetExitCode(-1);
        }

        public void SetExitCode(int exitCode)
        {
            this.ExitCode = exitCode;
        }

        #endregion
    }
}